package com.adee.java.jdbc.pooled;

import com.adee.java.jdbc.util.JdkProxyUtil;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

public class MyDatasource implements DataSource {

    private String url = "jdbc:mysql://8.142.104.99:3306/db1?characterEncoding=utf8";
    private String user = "root";
    private String password = "5UNRltoxlye6Zpjd";
    private String driverClass = "com.mysql.cj.jdbc.Driver";
    // private int initialPoolSize = 3;
    private int minPoolSize = 1;
    private int maxPoolSize = 5;
    // Connection连接池
    private BlockingQueue<Connection> pool;
    // 记录Connection总数
    private AtomicInteger remain;

    // 从本包中查找文件名propertiesFileName，path 不以’/'开头时默认是从此类所在的包下取资源，以’/'开头则是从ClassPath根下获取
    public MyDatasource(String propertiesFileName) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("MyDatasource.properties");
        Properties properties = new Properties();
        try {
            properties.load(is);
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driverClass = properties.getProperty("driverClass");
            // initialPoolSize = Integer.parseInt(properties.getProperty("initialPoolSize"));
            minPoolSize = Integer.parseInt(properties.getProperty("minPoolSize"));
            maxPoolSize = Integer.parseInt(properties.getProperty("maxPoolSize"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        pool = new LinkedBlockingDeque(maxPoolSize);
        remain = new AtomicInteger(maxPoolSize);
    }

    public MyDatasource() {
        this("MyDatasource.properties");
    }


    public static void main(String[] args) throws SQLException {
        // System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        MyDatasource ds = new MyDatasource();
        Connection connection = ds.getConnection();
    }

    // 初始化pool，创建minPoolSize个Connection，放进pool中
    private void initPool() {

    }

    // 创建新的连接
    private synchronized Connection newConnection(String user, String password) throws SQLException {
        int r = remain.get();
        if(r < 0) {  // 连接数量超过maxPooSize，报错，这种情况不会出现
            throw new SQLException("连接数量不可超过" + maxPoolSize);
        } else if(r == 0) { // 连接数量等于maxPoolSize，从池里获取，不再创建新的连接
            try {
                return pool.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new SQLException(e.getMessage());
            }
        } else { // 连接数量小于maxPoolSize，不论池里有没有空闲连接，都创建新的连接
            Connection conn = DriverManager.getConnection(url, user, password);
            Connection proxy1 = JdkProxyUtil.getProxy(Connection.class, (proxy, method, args) -> {
                if("close".equals(method.getName())) {
                    pool.put((Connection) proxy);
                    return null;
                } else {
                    return method.invoke(conn, args);
                }
            });
            remain.decrementAndGet();
            return proxy1;
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        return getConnection(user, password);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        int r = remain.get();
        if(r == 0) {
            try {
                return pool.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new SQLException(e.getMessage());
            }
            // remain != 0，在newConnection()内部会再次判断remain的值，此处用的是synchronized内外双重判断的技巧，
            // 可避免非必要进入synchronized内部
        } else {
            return newConnection(username, password);
        }
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return (T)this;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return DataSource.class.equals(iface);
    }
}


